package com.only4play.configuration.resttemplate;

import lombok.extern.slf4j.Slf4j;

import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.message.BasicHeaderElementIterator;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 优雅的http请求方式RestTemplate
 */

@Deprecated(since = "暂时启用")
@Slf4j
@ConditionalOnExpression("${spring.restTemplate.httpclient.maxTotal:0} > 0")
public class RestTemplateHttpClientConfig {


    //==============Resttemplate相关参数====================
    @Value("${spring.restTemplate.readTimeout:5000}")
    private int readTimeout;
    @Value("${spring.restTemplate.connectTimeout:2000}")
    private int connectTimeout;

    //==============Httpclient连接池相关参数====================
    @Value("${spring.restTemplate.httpclient.maxTotal:100}")
    private int maxTotal;
    @Value("${spring.restTemplate.httpclient.maxConnectPerRoute:40}")
    private int maxConnectPerRoute;
    @Value("${spring.restTemplate.httpclient.enableRetry:false}")
    private boolean enableRetry;
    @Value("${spring.restTemplate.httpclient.retryTimes:0}")
    private int retryTimes;
    @Value("${spring.restTemplate.httpclient.connectionRequestTimeout:1000}")
    private int connectionRequestTimeout;
    @Value("#{${spring.restTemplate.httpclient.keepAliveTargetHosts:{}}}")
    private Map<String, Long> keepAliveTargetHosts;
    @Value("${spring.restTemplate.httpclient.keepAliveTime:10000}")
    private long keepAliveTime;

    /**
     * 配置httpclient工厂
     *
     * @param httpClient
     * @return
     */
    @Bean
    @Primary
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient);
        //客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错
        factory.setConnectTimeout(connectTimeout); //毫秒
        //即socketTime,数据响应读取超时时间，指的是两个相邻的数据包的间隔超时时间
//        factory.setReadTimeout(readTimeout); //毫秒
        //连接不够用时从连接池获取连接的等待时间，必须设置。不宜过长，连接不够用时，等待时间过长将是灾难性的
        factory.setConnectionRequestTimeout(connectionRequestTimeout); //毫秒
        //必须使用BufferingClientHttpRequestFactory这个包装类，否则默认实现只允许读取一次响应流，日志输出那里读取之后，请求调用处再读取时会报错
        return new BufferingClientHttpRequestFactory(factory);
    }

    /**
     * 配置httpclient
     *
     * @return
     */
    @Bean
    public HttpClient httpClient(ConnectionKeepAliveStrategy connectionKeepAliveStrategy) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        SSLContext sslContext = null;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, (X509Certificate[] var1, String var2) -> true).build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            log.error(e.getMessage());
        }
        assert sslContext != null;
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
            // 注册http和https请求
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslConnectionSocketFactory)
            .build();
        System.out.println("======== 创建httpclient链接池配置");
        //使用Httpclient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        // 最大tcp连接数
        poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
        // 同路由最大tcp连接数
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxConnectPerRoute);
        //配置连接池
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        // 是否允许重试和重试次数
        httpClientBuilder.setRetryStrategy(new DefaultHttpRequestRetryStrategy(retryTimes, TimeValue.ZERO_MILLISECONDS));
        //设置长连接保持策略
        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
        return httpClientBuilder.build();
    }

    /**
     * 配置长连接策略
     *
     * @return
     */
    @Bean
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, context) -> {
            // Honor 'keep-alive' header
            BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HttpHeaders.KEEP_ALIVE));
            log.debug("HeaderElement:{}", it);
            while (it.hasNext()) {
                HeaderElement he = it.next();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && "timeout".equalsIgnoreCase(param)) {
                    try {
                        //1.服务器有时候会告诉客户端长连接的超时时间，如果有则设置为服务器的返回值
                        return TimeValue.ofDays(Long.parseLong(value) * 1000);
                    } catch (NumberFormatException ignore) {
                        log.error("解析长连接过期时间异常", ignore);
                    }
                }
            }
            //2.如果服务器没有返回超时时间则采用配置的时间
            //a.如果请求目标地址,单独配置了长连接保持时间,使用该配置 b.否则使用配置的的默认长连接保持时间keepAliveTime
            HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_REQUEST);
            Optional<Map.Entry<String, Long>> any = Optional.ofNullable(keepAliveTargetHosts).orElseGet(HashMap::new)
                .entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
            return TimeValue.ofDays(any.map(Map.Entry::getValue).orElse(keepAliveTime));
        };
    }

}
